<html>
<head>
  <style>
  
    popup.bubble
    {
      prototype: BubblePopup;
      background-color: transparent; // must be exactly transparent to create transparent window 
      width:400dip;
      height:300dip;
      cursor:pointer;
      padding:18dip; // room for the shadow 
      cursor: default;
      margin:2dip; // offset from anchor element
      border:1dip solid #ddd;
      var(shape-color): #fff; // shape background color
    }
    popup.bubble > .content {
      padding:12dip;
      size:*;
    }
    
    div#anchor
    {
      border:1dip solid blue;
      width:50dip;
      height:50dip;
      cursor:pointer;
    }
  </style>  
  <style #actions>
    div#anchor
    {
      behavior: button; 
      margin:0 *;
    }
  </style>    
  <script type="text/tiscript">
    
      $(div#anchor) << event click
      {
        this.popup( $(popup.bubble), (8 /*popup middle-top*/ << 16) | 2 /*at anchor's middle-bottom*/);
      } 
      
      class BubblePopup: Element {
      
        const RADIUS = 10dip;
        const ARROW_HEIGHT = 12dip;
      
        function attached() { 
          this.paintBackground = this.drawBackground;
        }
      
        function getShape()
        {
          var shape = this.shape;
          if(!shape) 
          {
            shape = this.shape = new Graphics.Path();
            this.shapeWidth = null;
            this.shapeHeight = null;
          }
         
          var (x,y,w,h) = this.$(div.content).box(#rectw,#border,#parent);
         
          if( this.shapeWidth == w && this.shapeHeight == h)
            return shape; // nothing to do already calculated
          
          shape.reset();

          const borderWidth = this.style#border-top-width;          

          const stoke_width = this.toPixels(borderWidth,#width);
          
          // lines shall go through pixels middles:
          x += stoke_width / 2; y -= stoke_width / 2; w -= stoke_width; h -= stoke_width;
          
          const r = this.toPixels(RADIUS,#width);
          const ah = this.toPixels(ARROW_HEIGHT,#height);
          
          shape          
            .moveTo(x+r, y)
            // that arrow
            .lineTo(x+w/2-ah,y) 
            .lineTo(x+w/2,y-ah)
            .lineTo(x+w/2+ah,y)
            // round rect
            .arcTo(x+w, y,   x+w, y+h, r)
            .arcTo(x+w, y+h, x,   y+h, r)
            .arcTo(x,   y+h, x,   y,   r)
            .arcTo(x,   y,   x+w, y,   r)              
            .close();
          return shape; 
        }      
      
        function drawBackground( gfx ) {

          if(var shape = this.getShape()) {

            const filter = [#drop-shadow: 6dip, 6dip, 6dip, rgba(0,0,0,0.60)]; // CSS: drop-sahdow(...);
            const backColor = this.style.variable("shape-color");
            const hasBorder = this.style#border-top-style == "solid";

             gfx.pushLayer(#margin-box,filter);

            if( hasBorder ) {
              const borderColor = this.style#border-top-color;
              const borderWidth = this.style#border-top-width;
              gfx.lineWidth(borderWidth)
                 .lineColor(borderColor)
                 .fillColor(backColor)
                 .drawPath( shape );
            } else 
              gfx.fillColor(backColor)
                 .drawPath( shape );

            gfx.popLayer();

            return true;
          }
        }
      }
      
      
    </script>
  
</head>
<body>
   <p>HTML/CSS popup with custom shape</p>
   <div#anchor> 
      click to reveal popup
   </div>
    <popup .bubble>
      <div.content>Content</div>
    </popup>
    

</body>
</html>

